<?php
#+------------------------------------------------------------------
#| 我曾七次鄙视自己的灵魂:
#| 第一次,当它本可进取时，却故作谦卑；
#| 第二次,当它空虚时，用爱欲来填充；
#| 第三次,在困难和容易之间，它选择了容易；
#| 第四次,它犯了错，却借由别人也会犯错来宽慰自己；
#| 第五次,它自由软弱，却把它认为是生命的坚韧；
#| 第六次,当它鄙夷一张丑恶的嘴脸时，却不知那正是自己面具中的一副；
#| 第七次,它侧身于生活的污泥中虽不甘心，却又畏首畏尾。
#+------------------------------------------------------------------
#| Author:Janmas Cromwell <janmas-cromwell@outlook.com>
#+------------------------------------------------------------------
namespace app\common\lib\short;

use app\common\lib\Identity;
use app\common\lib\queue\SaveShortUrl;
use app\common\lib\queue\VisitShortUrl;
use app\common\model\ShortUrlLog;
use app\common\model\User;
use think\Exception;
use think\facade\Queue;

/**
 * 短网址生成
 */
class Short
{
    public static $instance = null;

    protected $redis;

    protected $user;

    protected $scene = [
        'normal', 'permanent', 'temporary'
    ];

    public static function instance(\Redis $redis)
    {
        if (!self::$instance instanceof self) {
            self::$instance = new self($redis);
        }

        return self::$instance;
    }

    public function __construct(\Redis $redis)
    {
        $this->redis = $redis;
    }

    /**
     * 生成（登录用户）
     * @param $url
     * @param string $scene
     * @param User $user
     * @return array
     * @throws Exception
     */
    public function generate($url, string $scene = 'normal', User $user)
    {
        if (!in_array($scene, $this->scene)) {
            throw new Exception('不支持的链接留存类型', 20002);
        }
        //网站合法性检测
        (new Validity())->check($url);
        return $this->execute($url, $scene, $user);
    }

    /**
     * 批量生成
     * @param $urlMap
     * @param User $user
     * @return array
     */
    public function batch($urlMap, User $user)
    {
        $data = [];
        $error = [];
        foreach ($urlMap as $value) {
            try {
                $data[] = $this->generate($value['url'], $value['scene'], $user);
            } catch (\Exception $exception) {
                $error[] = $value['url'];
                continue;
            }
        }

        return [
            'data'    => $data,
            'error'   => $error,
            'message' => '一共传输了' . count($urlMap) . '个，其中成功了' . count($data) . '个,失败了' . count($error) . '个'
        ];
    }

    /**
     * 解析
     * @param string $short
     * @return false|string|void
     * @throws Exception
     */
    public function parse(string $short)
    {
        $this->redis->select(1);
        file_put_contents(app()->getRootPath() . 'runtime/server.log',var_export(request()->server(),true).PHP_EOL,FILE_APPEND);
        Queue::push(VisitShortUrl::class, [
            'short'   => $short,
            'ip'      => request()->ip(),
            'device'  => Identity::Os(),
            'referer' => isset($_SERVER['HTTP_REFERER'])?$_SERVER['HTTP_REFERER']:'',
        ], 'VISIT_SHORT_URL');
        if ($this->redis->exists($short) && $this->redis->hGet($short, 'status') == ShortUrlLog::STATUS_NORMAL) {
            return $this->redis->hGet($short, 'url');
        }
        $info = ShortUrlLog::where('short_code', $short)->findOrEmpty();
        if ($info->isEmpty()) {
            throw new Exception('指定的短网址不存在');
        }
        if ($info->status === ShortUrlLog::STATUS_OVERDUE) {
            throw new Exception('链接已过期');
        }
        if ($info->status === ShortUrlLog::STATUS_FORBIDEN) {
            throw new Exception('链接已被禁用');
        }
        return $info['url'];
    }

    /**
     * 未登录生成
     * @param string $url
     * @return array
     * @throws Exception
     */
    public function create(string $url)
    {
        (new Validity())->check($url);
        return $this->execute($url, ShortUrlLog::NORMAL);
    }

    private function getServerName()
    {
        /*$http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
        return $http_type.$_SERVER['HTTP_HOST'].'/a/';*/

        return request()->domain() . '/s/';
    }

    /**
     * url生成code
     * @param string $url
     * @return string
     */
    private function shortCodeCreate(string $url)
    {
        $crc = crc32($url);
        $x = sprintf("%u", $crc);
        $show = '';
        while ($x > 0) {
            $s = $x % 62;
            if ($s > 35) {
                $s = chr($s + 61);
            } elseif ($s > 9 && $s <= 35) {
                $s = chr($s + 55);
            }
            $show .= $s;
            $x = floor($x / 62);
        }
        return $show;

    }

    /**
     * 存储短链接
     * @param string $url
     * @param string $scene
     * @param User|null $user
     * @return array
     * @throws Exception
     */
    private function execute(string $url, string $scene, User $user = null)
    {
        $config = config('system.' . $scene);
        $time = time();
        $expire_time = $config['life_time'] > 0 ? $time + $config['life_time'] : 0;
        if (!is_null($user)) {
            if ($config['max'] <= $user->getLinkNumber($scene)) {
                throw new Exception('您持有的此类型链接已超过系统限制', 20003);
            }

            if ($user->money < $config['price']) {
                throw new Exception('余额不足', 40004);
            }
        }

        $shortCode = $this->shortCodeCreate($url);
        if ($this->redis->exists($shortCode)) {
            $userId = $this->redis->hGet($shortCode, 'user_id');
            if (!is_null($user) && $userId != $user->id) {
                throw new Exception('该网址已经存在', 40006);
            }
            if($scene !== $this->redis->hGet($shortCode,'scene')){
                throw new Exception('该链接已有其他类型短链接');
            }

            $return = [
                'origin'      => $this->redis->hGet($shortCode, 'url'),
                'scene'       => $this->redis->hGet($shortCode, 'scene'),
                'expire_time' => $this->redis->hGet($shortCode, 'expire_time'),
                'short'       => $this->getServerName() . $shortCode
            ];
            if ($return['expire_time'] != 0 && $return['expire_time'] > time()) {
                return $return;
            }
        }
        $return = [
            'origin'      => $url,
            'short'       => $this->getServerName() . $shortCode,
            'expire_time' => $expire_time,
            'scene'       => $scene
        ];

        $return['expire_time'] = $expire_time;

        $this->redis->select(1);
        $this->redis->hSet($shortCode, 'url', $url);
        $this->redis->hSet($shortCode, 'expire_time', $expire_time);
        $this->redis->hSet($shortCode, 'create_time', $time);
        $this->redis->hSet($shortCode, 'scene', $scene);
        $this->redis->hSet($shortCode, 'status', ShortUrlLog::STATUS_NORMAL);

        if (is_null($user)) {
            $this->redis->hSet($shortCode, 'type', 'tmp');
            return $return;
        }
        $this->redis->hSet($shortCode, 'user_id', $user->id);
        $this->redis->hSet($shortCode, 'type', 'user');
        //保存redis数据到数据库
        \think\facade\Queue::push(SaveShortUrl::class, [
            'data'  => [
                'url'         => $url,
                'user_id'     => $user->id,
                'expire_time' => $expire_time,
                'create_time' => $time,
                'scene'       => $scene,
                'short_code'  => $shortCode
            ],
            'user_id'  => $user->id,
            'price' => $config['price']
        ], 'SAVE_SHORTURL');
        return $return;
    }
}
